--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit 345a1d1f8af18586a9efb42db86870f55fca5d4c
Parents : 63f836b
Author : Ivan <e46112d44649266d71fe2193e00a4710>
Signature : T66BB85Valid, signed by author
Date : 2026-07-18T05:35:23-05:00
feat: fix _collect_read_roots to include virtual environment root for pyvenv.cfg access
Changes
3 files changed, 47 insertions(+), 2 deletions(-)
Diff
diff --git a/meshchatx.rsm b/meshchatx.rsm
index e9564215..d27fa721 100644
Binary files a/meshchatx.rsm and b/meshchatx.rsm differ
diff --git a/meshchatx/src/backend/landlock_sandbox.py b/meshchatx/src/backend/landlock_sandbox.py
index cfe38f0b..7a2958ff 100644
--- a/meshchatx/src/backend/landlock_sandbox.py
+++ b/meshchatx/src/backend/landlock_sandbox.py
@@ -353,8 +353,20 @@ def _collect_read_roots() -> list[str]:
exe_dir = _existing_dir(os.path.dirname(candidate))
if exe_dir:
roots.add(exe_dir)
- # Prefer the install prefix (…/cpython-…/) so bin + lib are covered.
- prefix = _existing_dir(getattr(sys, "base_prefix", None) or sys.prefix)
+ # Venv layouts put pyvenv.cfg next to bin/, not under it. Allowing only
+ # …/bin leaves child interpreters unable to read pyvenv.cfg (EACCES).
+ venv_root = os.path.dirname(exe_dir) if exe_dir else None
+ if venv_root and os.path.isfile(os.path.join(venv_root, "pyvenv.cfg")):
+ existing_venv = _existing_dir(venv_root)
+ if existing_venv:
+ roots.add(existing_venv)
+ # Prefer the install prefix (…/cpython-…/) so bin + lib are covered.
+ for prefix_candidate in (
+ getattr(sys, "base_prefix", None),
+ sys.prefix,
+ os.environ.get("VIRTUAL_ENV"),
+ ):
+ prefix = _existing_dir(prefix_candidate)
if prefix:
roots.add(prefix)
return sorted(roots)
diff --git a/tests/backend/test_landlock_sandbox.py b/tests/backend/test_landlock_sandbox.py
index 63ef1e94..5e417f91 100644
--- a/tests/backend/test_landlock_sandbox.py
+++ b/tests/backend/test_landlock_sandbox.py
@@ -107,6 +107,39 @@ def test_collect_read_roots_includes_interpreter_prefix():
assert any(
prefix == root or prefix.startswith(root.rstrip("/") + "/") for root in roots
), f"prefix {prefix!r} not covered by {roots!r}"
+ # Active venv root (sys.prefix) must be allowed even when base_prefix differs,
+ # otherwise child Python cannot read pyvenv.cfg (Docker /opt/venv + rnsh).
+ venv_prefix = os.path.realpath(sys.prefix)
+ assert any(
+ venv_prefix == root or venv_prefix.startswith(root.rstrip("/") + "/")
+ for root in roots
+ ), f"sys.prefix {venv_prefix!r} not covered by {roots!r}"
+
+
+def test_collect_read_roots_includes_venv_root_for_pyvenv_cfg(tmp_path, monkeypatch):
+ """Landlock must allow the venv root, not only …/bin (pyvenv.cfg sibling)."""
+ venv = tmp_path / "opt" / "venv"
+ bindir = venv / "bin"
+ bindir.mkdir(parents=True)
+ (venv / "pyvenv.cfg").write_text("home = /usr\n", encoding="utf-8")
+ fake_python = bindir / "python"
+ fake_python.write_text("#!/bin/sh\n", encoding="utf-8")
+
+ class _FakeSys:
+ platform = sys.platform
+ executable = str(fake_python)
+ prefix = str(venv)
+ base_prefix = "/usr"
+ path = list(sys.path)
+
+ monkeypatch.setattr(ll, "sys", _FakeSys)
+ monkeypatch.setattr(ll.site, "getsitepackages", lambda: [])
+ monkeypatch.setattr(ll.site, "getusersitepackages", lambda: "")
+ monkeypatch.setenv("VIRTUAL_ENV", str(venv))
+
+ roots = {os.path.realpath(r) for r in ll._collect_read_roots()}
+ assert os.path.realpath(str(venv)) in roots
+ assert os.path.realpath(str(bindir)) in roots
def test_handled_access_fs_for_abi_gates_new_rights():
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────